<template>
  <div class="studio column">
    <div class="row col">
      <studio-page-left @toggleMiniMode="toggleMiniMode">
        <template v-slot:project-list>
          <project-list class="col" :list="projectList" />
        </template>
      </studio-page-left>
      <div ref="element" class="video-container col">
        <teleport
          v-for="{ id, type, element } in componentInstances"
          :key="id"
          :to="element"
        >
          <component
            :is="type"
            @inited="onVideoPlayerInited"
            @audioReady="onAudioReady"
            @clicked="resumeAudio"
            @addCaption="addCaption"
          ></component>
        </teleport>
      </div>
    </div>
    <studio-bottom
      @startRecoding="startRecoding"
      @stopRecoding="stopRecoding"
      @addCaption="addCaption"
      @publish="onPublish"
      @save="onSave"
      @forward="onForward"
      @backward="onBackward"
      @previous="onPrevious"
      @next="onNext"
      @undo="onUndo"
      @redo="onRedo"
      @splitBlock="onSplitBlock"
    >
      <template v-slot:track-scroller>
        <TrackScroller
          :duration="duration"
          v-model:position="trackScrollPosition"
          v-model:zoom="zoom"
        />
      </template>
      <template v-slot:timeline>
        <Timeline
          :duration="duration"
          v-model:position="trackScrollPosition"
          :zoom="zoom"
        />
      </template>
      <template v-slot:record-track>
        <div class="fit">
          <AudioTrack
            ref="recordTrack"
            :duration="duration"
            :zoom="zoom"
            :scrollPosition="trackScrollPosition"
            :isRecording="isRecording"
            handle
            @select="onAudioTrackSelect"
          ></AudioTrack>
        </div>
      </template>
      <template v-slot:caption-track>
        <div class="fit">
          <TextTrack
            ref="captionTrack"
            :duration="duration"
            :zoom="zoom"
            :scrollPosition="trackScrollPosition"
            @select="onTextTrackSelect"
          />
        </div>
      </template>
      <template v-slot:video-track>
        <div class="fit">
          <AudioTrack
            ref="videoTrack"
            :duration="duration"
            :zoom="zoom"
            :scrollPosition="trackScrollPosition"
          ></AudioTrack>
        </div>
      </template>
      <template v-slot:mic-peak>
        <div style="width: 100%; height: 100%" ref="micPeak"></div>
      </template>
      <template v-slot:video-peak>
        <div style="width: 100%; height: 100%" ref="videoPeak"></div>
      </template>
    </studio-bottom>
    <q-dialog v-model="showAddCaptionDialog">
      <q-card style="min-width: 350px">
        <q-card-section>
          <div class="text-h6">
            {{ $t('studio.captionPanel.addNewCaption') }}
          </div>
        </q-card-section>
        <q-card-section>
          <q-input
            type="textarea"
            dense
            v-model="captionLine"
            autofocus
            autogrow
          />
        </q-card-section>
        <q-card-actions align="right" class="text-primary">
          <q-btn flat color="sub" :label="$t('common.cancel')" v-close-popup />
          <q-btn
            flat
            color="accent"
            :label="$t('common.add')"
            @click="confirmNewCaption"
            v-close-popup
          />
        </q-card-actions>
      </q-card>
    </q-dialog>
    <q-dialog
      v-model="showPublishDialog"
      maximized
      transition-show="slide-down"
      transition-hide="slide-up"
      v-if="currentVideo"
      persistent
    >
      <publish-dialog :video="currentVideo" />
    </q-dialog>
    <q-dialog
      v-model="showGuestView"
      maximized
      transition-show="slide-down"
      transition-hide="slide-up"
    >
      <studio-guest-view />
    </q-dialog>
  </div>
</template>
<script lang="ts">
import Vue, {
  defineComponent,
  ref,
  onBeforeMount,
  onMounted,
  computed,
  nextTick,
  watch,
} from 'vue';
import { useRouter } from 'vue-router';
import { Dialog, Loading, useQuasar } from 'quasar';
import StudioBottom from './studio/StudioBottom.vue';
import TrackScroller from './studio/TrackScroller.vue';
import VideoPlayer from 'components/video/VideoPlayer.vue';
import CaptionEditorPanel from 'components/panel/CaptionEditorPanel.vue';
import CaptionsPanel from 'components/panel/CaptionsPanel.vue';
import StudioPageLeft from './studio/StudioPageLeft.vue';
import ProjectList from './studio/ProjectList.vue';
import { Project } from './studio/StudioProject';
import AudioTrack from './studio/AudioTrack.vue';
import { LayoutConfig } from 'golden-layout';
import useGoldenLayout from 'src/mixins/GoldenLayout';
import { VideoJsPlayer } from 'video.js';
import Video from 'src/models/Video';
import Audio from 'src/models/Audio';
import AudioMixer, { Channel } from 'src/utils/AudioMixer';
import VoiceRecorder from 'src/utils/VoiceRecorder';
import Timeline from './studio/Timeline.vue';
import TextTrack from './studio/TextTrack.vue';
import StudioGuestView from './studio/GuestView.vue';
import { useI18n } from 'vue-i18n';
import StudioProject from './studio/StudioProject';
import AudioBlockInfo from './studio/blocks/AudioBlockInfo';
import useUndoRedo from 'src/mixins/UndoRedo';
import PublishDialog from './studio/PublishDialog.vue';
import { useUserStore } from 'src/stores/user-store';
import { useVideoStore } from 'src/stores/video-store';
import { useStudioStore } from 'stores/studio-store';
const components = {
  VideoPlayer,
  CaptionEditorPanel,
};
export default defineComponent({
  components: {
    ...components,
    StudioBottom,
    StudioPageLeft,
    ProjectList,
    AudioTrack,
    TrackScroller,
    Timeline,
    TextTrack,
    StudioGuestView,
    PublishDialog,
  },
  setup() {
    //-------------------------init-------------------------------
    const userStore = useUserStore();
    const videoStore = useVideoStore();
    const studioStore = useStudioStore();
    const router = useRouter();
    const $q = useQuasar();
    const { t } = useI18n();
    const currentUser = computed(() => userStore.currentUser);
    let videoPlayer: VideoJsPlayer | null = null;
    const currentPosition = computed<number>({
      get() {
        return videoStore.currentTime;
      },
      set(val: number) {
        videoStore.currentTime = val;
      },
    });
    const { canUndo, canRedo, undo, redo } = useUndoRedo();
    $q.loading.show();
    const mixer = new AudioMixer();
    let videoAudioBuffer: AudioBuffer | null = null;
    const forwardSeconds = 5;
    const duration = ref(0);
    const videoChannel = ref<Channel>(mixer.createChannel('video'));
    const voiceChannel = ref<Channel>(mixer.createChannel());
    const videoId = router.currentRoute.value.params.id as string;
    const currentVideo = computed<Video | null>(() => videoStore.currentVideo);
    const currentAudio = computed<Audio | null>(() => videoStore.currentAudio);
    const isPlaying = computed<boolean>(() => videoStore.isPlaying);
    const currentProject = computed<Project>(() => videoStore.currentProject);
    const projectName = computed<string>(() => videoStore.projectName);
    const zoom = computed<number>({
      get() {
        return videoStore.zoom;
      },
      set(val: number) {
        videoStore.zoom = val;
      },
    });
    onBeforeMount(async () => {
      if (!currentVideo.value) {
        await videoStore.getVideoAndPlay(videoId);
      }
    });
    onMounted(() => {
      if (micPeak.value) {
        mixer.createAudioPeak(micPeak.value);
      }
      if (videoPeak.value) {
        mixer.createAudioPeak(videoPeak.value, 'video');
      }
    });
    const projectList = ref<Project[]>([]);
    const showAddCaptionDialog = ref(false);
    watch(showAddCaptionDialog, () => {
      studioStore.isTyping = showAddCaptionDialog.value;
    });
    const captionLine = ref('');
    const showGuestView = ref(false);
    const showPublishDialog = ref(false);
    //----------------Studio Project----------------------
    let project = new StudioProject(videoId as string);
    project.initDB().then(async () => {
      projectList.value = await project.getProjects();
    });
    function restoreProject(project: Project | null = null) {
      if (project) {
        videoStore.currentProject = project;
      } else {
        videoStore.currentProject = projectList.value[0];
      }
      videoStore.projectName = currentProject.value.name;
      videoStore.setCurrnetTime(currentProject.value.position | 0);
      zoom.value = currentProject.value.zoom;
      recordVolume.value = currentProject.value.recordVolume;
      // videoVolume.value = currentProject.value.videoVolume;
      if (currentProject.value.voiceList.length > 0) {
        recordTrack.value.restore(currentProject.value.voiceList);
      }
      if (currentProject.value.captionList.length > 0) {
        captionTrack.value.restore(currentProject.value.captionList);
      }
    }
    async function onSave() {
      const recordList = recordTrack.value.getBlockListInfo();
      if (recordList.length > 0) {
        project.storeVoiceList(recordList);
      }
      const captionList = captionTrack.value.getCaptionList();
      if (captionList.length > 0) {
        project.storeCaptionList(captionList);
      }
      await project.storeProject(
        projectName.value,
        currentPosition.value,
        zoom.value,
        recordVolume.value
      );
      projectList.value = await project.getProjects();
      $q.notify({
        message: 'SAVED',
        icon: 'mdi-check-circle',
        color: 'positive',
      });
    }
    //----------------Voice Recorder----------------------
    const voiceRecorder: VoiceRecorder = new VoiceRecorder();
    let recordStartTime = 0;
    const isRecording = computed<boolean>(() => videoStore.isRecording);
    const recordVolume = computed<number>({
      get() {
        return videoStore.recordVolume;
      },
      set(value: number) {
        videoStore.recordVolume = value;
      },
    });
    voiceRecorder.on('onStart', async () => {
      videoStore.isRecording = true;
      recordStartTime = currentPosition.value;
      await videoPlayer!.play();
    });
    voiceRecorder.on('onStop', (data: Blob) => {
      videoStore.isRecording = false;
      videoPlayer!.pause();
      if (recordTrack.value) {
        recordTrack.value.addAudioBlock2(data, recordStartTime);
      }
    });
    //----------------golden-layout-----------------------
    const layoutConfig: LayoutConfig = {
      settings: {
        showCloseIcon: true,
        showMaximiseIcon: false,
        showPopoutIcon: false,
      },
      dimensions: {
        borderWidth: 5,
        headerHeight: 30,
      },
      root: {
        type: 'row',
        content: [
          {
            type: 'column',
            width: 70,
            content: [
              {
                type: 'component',
                componentType: 'VideoPlayer',
                id: 'videoPlayer',
                height: 100,
                isClosable: false,
                header: {
                  show: false,
                },
              },
            ],
          },
          {
            type: 'stack',
            width: 30,
            content: [
              {
                type: 'component',
                componentType: 'CaptionEditorPanel',
                title: t('studio.captionPanel.captionEditorPanelTitle'),
                isClosable: false,
              },
            ],
          },
        ],
      },
    };
    const { element, componentInstances, layout } = useGoldenLayout(
      {
        VideoPlayer,
        CaptionEditorPanel,
      },
      layoutConfig
    );
    //------------------------refs----------------------
    const timeline = ref<HTMLDivElement>();
    const recordTrack = ref<any>();
    const captionTrack = ref<any>();
    const videoTrack = ref<any>();
    const videoPeak = ref<HTMLDivElement>();
    const micPeak = ref<HTMLDivElement>();
    //----------------------endref----------------------
    const trackScrollPosition = ref(0);
    function onVideoPlayerInited(
      player: VideoJsPlayer,
      element: HTMLVideoElement
    ) {
      videoPlayer = player;
    }
    function toggleMiniMode() {
      nextTick(() => {
        if (layout.value && element.value) {
          layout.value.setSize(
            element.value?.clientWidth,
            element.value?.clientHeight
          );
        }
      });
    }
    async function onAudioReady(audioEl: HTMLAudioElement) {
      // get audio channel data here
      console.log('start processing audio...');
      mixer.createAudioSource(audioEl, 'video');
      videoAudioBuffer = await mixer.getAudioBufferFromElement(audioEl);
      duration.value = videoAudioBuffer.duration;
      $q.loading.hide();
      console.log('audio processing finished');
      if (videoTrack.value) {
        videoTrack.value.addAudioBlock(
          videoAudioBuffer,
          0,
          null,
          'Video',
          false
        );
      }
      recordTrack.value.initChannel(voiceChannel.value, duration.value);
      if (projectList.value.length > 0) {
        restoreProject();
      }
    }
    function getNearestPosition(step = 1): number {
      if (recordTrack.value) {
        const voiceList: AudioBlockInfo[] =
          recordTrack.value.getBlockListInfo();
        const positionList: number[] = [];
        if (voiceList.length > 0) {
          voiceList.forEach((item) => {
            positionList.push(item.start);
            positionList.push((item.start = item.duration));
          });
          let nearestIndex = positionList.findIndex((value, index, arr) => {
            if (index === arr.length - 1) {
              return true;
            }
            const isNearest =
              Math.abs(value - currentPosition.value) <
              Math.abs(arr[index + 1] - currentPosition.value);
            return isNearest;
          });
          if (nearestIndex < 0) {
            nearestIndex = 0;
          }
          nearestIndex += step;
          if (nearestIndex < 0) {
            nearestIndex = positionList.length - 1;
          }
          if (nearestIndex > positionList.length - 1) {
            nearestIndex = 0;
          }
          return positionList[nearestIndex];
        } else {
          return 0;
        }
      } else {
        return 0;
      }
    }
    //----------------studio-bottom events------------------
    function startRecoding() {
      if (isRecording.value) {
        return;
      }
      voiceRecorder.start();
    }
    function stopRecoding() {
      if (isRecording.value) {
        voiceRecorder.stop();
      }
    }
    function onVideoTrackScroll(horizontalPosition: number) {
      trackScrollPosition.value = horizontalPosition;
    }
    function addCaption() {
      showAddCaptionDialog.value = true;
    }
    function confirmNewCaption() {
      if (captionLine.value && captionLine.value !== '') {
        captionTrack.value.addCaption(captionLine.value, currentPosition.value);
        captionLine.value = '';
      }
    }
    function onPublish() {
      if (!currentUser.value) {
        showGuestView.value = true;
        return;
      }
      showPublishDialog.value = true;
    }
    function onPrevious() {
      const position = getNearestPosition(-1);
      videoPlayer!.currentTime(position);
    }
    function onBackward() {
      videoPlayer!.currentTime(videoPlayer!.currentTime() - forwardSeconds);
    }
    function onForward() {
      videoPlayer!.currentTime(videoPlayer!.currentTime() + forwardSeconds);
    }
    function onNext() {
      const position = getNearestPosition();
      videoPlayer!.currentTime(position);
    }
    function onUndo() {
      const action = undo();
      if (action!.undo && action!.undoParams) {
        action!.undo(...action!.undoParams, false);
      }
    }
    function onRedo() {
      const action = redo();
      if (action!.redo && action!.redoParams) {
        action!.redo(...action!.redoParams, false);
      }
    }
    let currentSelectedTrack: string | null = null;
    let currentSelectedIndex: number | null = null;
    function onSplitBlock() {
      if (currentSelectedTrack !== null && currentSelectedIndex !== null) {
        if (currentSelectedTrack === 'audio') {
          recordTrack.value.split(currentSelectedIndex);
        } else {
          captionTrack.value.split(currentSelectedIndex);
        }
      }
    }
    function onAudioTrackSelect(index: number | null) {
      if (index !== null) {
        currentSelectedTrack = 'audio';
        currentSelectedIndex = index;
      } else {
        currentSelectedTrack = null;
        currentSelectedIndex = null;
      }
    }
    function onTextTrackSelect(index: number | null) {
      if (index !== null) {
        currentSelectedTrack = 'text';
        currentSelectedIndex = index;
      } else {
        currentSelectedTrack = null;
        currentSelectedIndex = null;
      }
    }
    function resetWorkspace() {
      zoom.value = 1;
      trackScrollPosition.value = 0;
      currentPosition.value = 0;
      recordTrack.value.clearAll();
    }
    return {
      currentVideo,
      duration,
      zoom,
      currentPosition,
      element,
      componentInstances,
      projectList,
      timeline,
      recordTrack,
      recordVolume,
      captionTrack,
      videoTrack,
      videoPeak,
      videoChannel,
      voiceChannel,
      micPeak,
      trackScrollPosition,
      isRecording,
      showAddCaptionDialog,
      captionLine,
      showGuestView,
      showPublishDialog,
      canUndo,
      canRedo,
      toggleMiniMode,
      onVideoPlayerInited,
      onAudioReady,
      onVideoTrackScroll,
      resumeAudio() {
        mixer.resume();
      },
      startRecoding,
      stopRecoding,
      addCaption,
      confirmNewCaption,
      onPublish,
      onSave,
      onForward,
      onBackward,
      onPrevious,
      onNext,
      onUndo,
      onRedo,
      onSplitBlock,
      onAudioTrackSelect,
      onTextTrackSelect,
    };
  },
});
</script>
<style lang="sass" scoped>

.studio
  background-color: #101010
  width: calc(100vw - 72px)
  height: calc(100vh - 50px)
  .video-container
    height: 100%
</style>
<style src="golden-layout/dist/css/goldenlayout-base.css"></style>
<style src="golden-layout/dist/css/themes/goldenlayout-dark-theme.css"></style>
